/* 
 * File:   main.cpp
 * Author: robert
 *
 * Created on 31. August 2009, 14:54
 */

#include <stdlib.h>
#include <sys/time.h>
#include <iostream>
#include <cmath>
#include <CLcalc.h>

using namespace std;

/*
 * 
 */
int main(int argc, char** argv) {

    //init the library
    int err = CLcalc_init();

    //The name of the first GPU and CPU:
    cout << "First GPU: " << CLcalc_getDeviceName(CLcalc_DEVICE_gpu0) << endl;
    int * speed = CLcalc_getDeviceSpeed(CLcalc_DEVICE_gpu0);
    if (speed[0] != -1)  {
        cout << "   max. frequency:  " << speed[0] << " MHz" << endl;
        cout << "   number of cores: " << speed[1] << endl;
        cout << "   freq. * cores:   " << speed[2] << " MHz" << endl;
    }
    cout << "First CPU: " << CLcalc_getDeviceName(CLcalc_DEVICE_cpu0) << endl;
    speed = CLcalc_getDeviceSpeed(CLcalc_DEVICE_cpu0);
    if (speed[0] != -1)  {
        cout << "   max. frequency:  " << speed[0] << " MHz" << endl;
        cout << "   number of cores: " << speed[1] << endl;
        cout << "   freq. * cores:   " << speed[2] << " MHz" << endl;
    }

    //Any errors?
    if (err != 0) {
        cout << "Error: " << err << endl;
        return (EXIT_FAILURE);
    }

    //execute a small sample
    int count = 3000;

    float* input = new float[count];
    float* output = new float[count];
    float* compare = new float[count];
    for (int i=0;i<count;i++) {
        input[i] = i/1000;
    }

    //the source code
    const char *source = 
    "__kernel void square(                                                      \
        __global float* input,                                                  \
        __global float* output,                                                 \
        const unsigned int count)                                               \
    {                                                                           \
        int i = get_global_id(0);                                               \
        output[i] = 0;                                                          \
        for (int j=i;j<count;j++)                                               \
            output[i] += round(sqrt(input[j]));                                 \
    }                                                                           \
    ";

    //execute the sample
    cout << endl << "Sample kernel: " << endl << endl;
    cout << source << endl << endl;
    cout << "Executing the kernel on the first GPU..." << endl;

    //compile the sample program
    int progid = CLcalc_BuildProgramFromSource(CLcalc_DEVICE_gpu0, source, "square");
    if (progid < 0) {
        return (EXIT_FAILURE);
    }

    double start = CLcalc_getTimeInMillis();

    err = CLcalc_exec_compiled(CLcalc_DEVICE_gpu0,
            progid,
            input, sizeof(float)*count, CL_MEM_READ_ONLY,
            output, sizeof(float)*count, CL_MEM_WRITE_ONLY,
            count);

    double ende = CLcalc_getTimeInMillis();

    //errors?
    if (err != 0) cout << "Error: " << err << endl;
    else {
        cout << "Successfull!" << endl;
        cout << (ende - start) << "ms" << endl;
    }

    //The same cal on the cpu
    start = CLcalc_getTimeInMillis();
    for (int i=0;i<count;i++) {
        compare[i] = 0;
        for (int j=i;j<count;j++)
            compare[i] += round(sqrt(input[j]));
    }
    ende = CLcalc_getTimeInMillis();
    cout << (ende - start) << "ms" << endl;

    //Compare the results
    for (int i=0;i<count;i++) {
        if (compare[i] != output[i]) {
            cout << "not equal! Diff:" << (compare[i] - output[i]) << endl;
            break;
        }
    }

    //The Same Exsample using the Executor-Interface----------------------------
    //which is more complicate but also more flexible---------------------------

    start = CLcalc_getTimeInMillis();

    //Create a new Executor
    int ExID = CLcalc_Executor_new(CLcalc_DEVICE_gpu0);
    cout << "Executor ID:    " << ExID << endl;

    //Add a Programm from source
    int progID = CLcalc_Executor_addProgFromSource(ExID, source, "square");
    cout << "Program ID:     " << progID << endl;

    //Add Data to The executor, every Program can use it
    int data1 = CLcalc_Executor_addData(ExID, 0, input, sizeof(float)*count, CL_MEM_READ_ONLY);
    cout << "Data 1 ID:      " << data1 << endl;
    int data2 = CLcalc_Executor_addData(ExID, 0, output, sizeof(float)*count, CL_MEM_WRITE_ONLY);
    cout << "Data 2 ID:      " << data2 << endl;

    //Set argument: data1, data2, data3 
    int err2 = CLcalc_Executor_setArgument(ExID,progID,0,data1);
    err2 |= CLcalc_Executor_setArgument(ExID,progID,1,data2);
    err2 |= CLcalc_Executor_setConstIntArgument(ExID,progID,2,count);
    if (err2 != 0) cout << "Error with the arguments" << endl;

    //Run the Programm
    err2 = CLcalc_Executor_Execute(ExID, 0, progID, count);
    if (err2 != 0) cout << "Error with the execution" << endl;

    //Run the Kernel again, that is a big advantage of the Executor-Interface
    //You can run kernels again. You can also change Data in between...
    err2 = CLcalc_Executor_Execute(ExID, 0, progID, count);
    if (err2 != 0) cout << "Error with the execution" << endl;

    //Read the Results
    err2 = CLcalc_Executor_getData(ExID,0,data2,output,sizeof(float)*count);
    if (err2 != 0) cout << "Error while reading the result" << endl;

    //Compare the results
    for (int i=0;i<count;i++) {
        if (compare[i] != output[i]) {
            cout << "not equal! Diff:" << (compare[i] - output[i]) << endl;
            break;
        }
    }

    ende = CLcalc_getTimeInMillis();
    cout << (ende - start) << "ms" << endl;

    //--------------------------------------------------------------------------
    //Try a build in function of the CLcalc-Library
    //--------------------------------------------------------------------------

    int lengths[2] = {1000,10000};

    float** sqrin = new float*[lengths[0]];
    sqrin[0] = new float[lengths[0]*lengths[1]];
    for (int i=1;i<lengths[0];i++)
        sqrin[i] = sqrin[i-1] + lengths[1];

    float** sqrout = new float*[lengths[0]];
    sqrout[0] = new float[lengths[0]*lengths[1]];
    for (int i=1;i<lengths[0];i++)
        sqrout[i] = sqrout[i-1] + lengths[1];

    for (int i=0;i<lengths[0];i++) {
        for (int j=0;j<lengths[1];j++) {
            sqrin[i][j] = i*j;
        }
    }

    start = CLcalc_getTimeInMillis();
    err = CLcalc_BuildIn_sqrt(ExID,0,sqrin[0], sqrout[0], lengths, 2);
    ende = CLcalc_getTimeInMillis();
    cout << (ende - start) << "ms" << endl;
    if (err != 0) cout << "Error in CLcalc_BuildIn_sqrt: " << err << endl;

    //compare to cpu-speed
    start = CLcalc_getTimeInMillis();
    for (int i=0;i<lengths[0];i++) {
        for (int j=0;j<lengths[1];j++) {
            sqrout[i][j] = sqrt(sqrin[i][j]);
        }
    }
    ende = CLcalc_getTimeInMillis();
    cout << (ende - start) << "ms" << endl;

    //the same exsample but with data transfered seperate of the execution------
    //--------------------------------------------------------------------------

    //transfer the data
    int inputID = CLcalc_Executor_addData(ExID, 0, sqrin[0], lengths[0]*lengths[1]*sizeof(float), CL_MEM_READ_ONLY);
    int outputID = CLcalc_Executor_addData(ExID, 0, sqrout[0], lengths[0]*lengths[1]*sizeof(float), CL_MEM_READ_WRITE);

    //calculate the square root
    start = CLcalc_getTimeInMillis();
    err = 0;
    for (int t=0;t<10;t++) {
        err |= CLcalc_BuildIn_sqrt_execOnly(ExID,0,inputID, outputID, lengths, 2);
        err |= CLcalc_BuildIn_basic_execOnly(ExID, 0, inputID, outputID, outputID, lengths, 2, CLcalc_BUILDIN_BASIC_ADDITION);
        err |= CLcalc_BuildIn_basic_execOnly(ExID, 0, inputID, outputID, outputID, lengths, 2, CLcalc_BUILDIN_BASIC_SUBSTRACTION);
        err |= CLcalc_BuildIn_basic_execOnly(ExID, 0, inputID, outputID, outputID, lengths, 2, CLcalc_BUILDIN_BASIC_MULTIPLICATION);
        err |= CLcalc_BuildIn_basic_execOnly(ExID, 0, inputID, outputID, outputID, lengths, 2, CLcalc_BUILDIN_BASIC_DIVISION);
    }
    ende = CLcalc_getTimeInMillis();
    if (err != 0) cout << "Error: " << err << endl;
    cout << "sqrt_execOnly: " << (ende - start) << "ms" << endl;

    //read the result
    err = CLcalc_Executor_getData(ExID, 0, outputID, sqrout[0], lengths[0]*lengths[1]*sizeof(float));
    if (err != 0) cout << "Error: " << err << endl;
    cout << " input[10][10] = " << sqrin[10][10] << endl;
    cout << "output[10][10] = " << sqrout[10][10] << endl;
    
    //Shutdown the Executor
    CLcalc_Executor_shutdown(ExID);
    cout << "Executor shutdown" << endl;


    //Shutdown the library
    CLcalc_shutdown();

    return (EXIT_SUCCESS);
}

